using System;
using System.Reflection;

namespace UnitTesting.Fixturing
{
	class Target<T> : IWithArgument<T>, IWithConstructor
	{
		readonly IConstructorSpecifier<T> _constructorSpecifier;
		readonly IArgumentSpecifier _argumentSpecifier;
		readonly TestObjectCreator<T> _testObjectCreator;

		internal Target(TargetParams<T> targetParams)
		{
			_constructorSpecifier = targetParams.ConstructorSpecifier;
			_argumentSpecifier = targetParams.ArgumentSpecifier;
			_testObjectCreator = targetParams.TestObjectCreator;
		}

		protected Target()
		{
		}

		/* Argument */

		public virtual IWithArgument<T> With<TArg>(TArg argument)
		{
			_argumentSpecifier.WithArgument(new GenericTypedArgument<TArg>(argument));
			return this;
		}

		public IWithArgument<T> With<TArg>(string name, TArg argument)
		{
			Ensure.IsNotNullOrEmpty(() => name);
			_argumentSpecifier.WithArgument(new NamedArgument<TArg>(name, argument));
			return this;
		}

		public IWithArgument<T> With<TArg>(int position, TArg argument)
		{
			_argumentSpecifier.WithArgument(new PositionedArgument<TArg>(position, argument));
			return this;
		}

		public IWithArgument<T> WithXth<TArg>(int xthTyped, TArg argument)
		{
			_argumentSpecifier.WithArgument(new XthTypedArgument<TArg>(xthTyped, argument));
			return this;
		}

		public IWithArgument<T> WithMany(params object[] arguments)
		{
			Ensure.IsNotAnyNull(() => arguments);
			arguments.ForEach(arg => _argumentSpecifier.WithArgument(new TypedArgument(arg)));
			return this;
		}

		/* Constructor */

		public void StrictVerbose()
		{
			_constructorSpecifier.WithConstructor(new VerboseConstructorSelector<T>(true));
		}

		public virtual void LooseVerbose()
		{
			_constructorSpecifier.WithConstructor(new VerboseConstructorSelector<T>(false));
		}

		public void StrictParamCount(int paramCount)
		{
			_constructorSpecifier.WithConstructor(new ParamCountConstructorSelector<T>(true, paramCount));
		}

		public void LooseParamCount(int paramCount)
		{
			_constructorSpecifier.WithConstructor(new ParamCountConstructorSelector<T>(false, paramCount));
		}

		public void ParamTypes(params Type[] types)
		{
			Ensure.IsNotNull(() => types);
			Ensure.IsNotAnyNull(() => types);
			_constructorSpecifier.WithConstructor(new ParamTypesConstructorSelector<T>(types));
		}

		public void Conditional(Func<ConstructorInfo[], ConstructorInfo> condition)
		{
			Ensure.IsNotNull(() => condition);
			_constructorSpecifier.WithConstructor(new ConditionalConstructorSelector<T>(condition));
		}

		public void Default()
		{
			LooseVerbose();
		}

		/* Create */

		internal virtual T Create()
		{
			return _testObjectCreator.Create();
		}

		internal virtual T Freeze()
		{
			return _testObjectCreator.Freeze();
		}
	}
}